<?php

Raise::load('core.RaiseTreeNode');
Raise::load('core.RaiseDomTreeNodeCollection');
Raise::load('core.RaiseHtmlTag');
Raise::load('core.RaiseCollection');

/**
 * RaiseDomTreeNode class
 * represents a node in the DOM Tree, which can contain more RaiseDomTreeNode or RaiseHtmlTag
 *
 * @author Sam-Mauris Yong <hellclanner at live dot com>
 * @license http://www.opensource.org/licenses/bsd-license New BSD License
 * @package Raise.Core.DOM
 * @since 1.2
 */
class RaiseDomTreeNode extends RaiseTreeNode {

    /**
     * The children nodes of this sub-tree
     * @var RaiseDomTreeNodeCollection
     */
    protected $children;

    /**
     * Create a new RaiseDomTreeNode
     */
    function __construct() {
        $this->children = new RaiseDomTreeNodeCollection();
    }

    /**
     * Get or set the children nodes of this RaiseDomTreeNode
     * @param RaiseDomTreeNodeCollection $c
     * @return RaiseDomTreeNodeCollection
     */
    public function children($c = false) {
        return parent::children($c);
    }

    /**
     * The RaiseHtmlTag associated with this node
     * @var RaiseHtmlTag
     */
    private $tag;

    /**
     * Get or set the RaiseHtmlTag associated with this node
     * @param RaiseHtmlTag $t (Optional) The RaiseHtmlTag to set
     * @return RaiseHtmlTag
     */
    public function tag($t = false){
        if($t !== false){
            if(!($t instanceof RaiseHtmlTag) && $t != null){
                throw new RaiseInvalidArgumentException('$tag must be an instance of RaiseHtmlTag or NULL.');
            }
            $this->tag = $t;
        }
        return $this->tag;
    }

    /**
     * Get elements using CSS selectors
     * @param string $selector
     * @return array
     */
    public function select($selector){
        $selector = trim($selector);
        
        if(strpos($selector, ',') !== false){
            $ex = explode(',', $selector);
            foreach($ex as $e){
                $this->select($s);
            }
        }else{
            if(!$selector){
                throw new RaiseInvalidArgumentException('CSS Selector cannot be empty');
            }

            $space = $this;

            $selLen = strlen($selector);
            $parsePos = 0;

            $lastKeyChar = false;
            
            for($startParsePos = $parsePos; $parsePos < $selLen + 1; $parsePos++){
                if($parsePos < $selLen){
                    $c = substr($selector, $parsePos, 1);
                }else{
                    $c = false;
                }
                if(!$c || in_array($c, array('#','.',':', ' '))){
                    if($lastKeyChar == false){
                        $tag = substr($selector, $startParsePos, $parsePos - $startParsePos);
                        if($tag){
                            $x = new RaiseDomTreeNode();
                            $x->children = new RaiseCollection($this->searchByTagName($space, $tag));
                            $space = $x;
                        }
                    }elseif($lastKeyChar == '#'){
                        $id = substr($selector, $startParsePos, $parsePos - $startParsePos);
                        $space = $this->searchById($space, $id);
                    }elseif($lastKeyChar == '.'){
                        $class = substr($selector, $startParsePos, $parsePos - $startParsePos);
                        $x = new RaiseDomTreeNode();
                        $x->children = new RaiseCollection($this->searchByClass($space, $class));
                        $space = $x;
                    }elseif($lastKeyChar == ':'){
                        $pseudo = substr($selector, $startParsePos, $parsePos - $startParsePos);
                        switch($pseudo){
                            case 'first':
                                if($space->children() == null){
                                    $space = null;
                                }else{
                                    $space = $space->children()->first();
                                }
                                break;
                            case 'last':
                                if($space->children() == null){
                                    $space = null;
                                }else{
                                    $space = $space->children()->last();
                                }
                                break;
                        }
                    }
                    if($space == null){
                        return null;
                    }
                    if(!$c){
                        break;
                    }
                    $startParsePos = $parsePos + 1;
                    $lastKeyChar = $c;
                }
                if($c == ' '){
                    $lastKeyChar = false;
                }
            }

        }
        return $space;
    }

    /**
     * Get elements by attribute and value combination recursively
     * @param string $name
     * @return RaiseCollection
     */
    public function getByClass($value){
        return new RaiseCollection($this->searchByClass($this, $value));
    }

    /**
     * Search the entire DOM tree by class name
     * @param RaiseDomTreeNode $a
     * @param string $id
     * @return array
     */
    private function searchByClass($a, $v){
        $r = array();
        if($a->children() != null){
            foreach($a->children() as $item){
                if($item->tag() != null){
                    $e = explode(' ', $item->tag()->attributes()->get('class'));
                    if(in_array($v, $e)){
                        $r[] = $item;
                    }
                }
                if($item->children() != null){
                    $b = $this->searchByClass($item, $v);
                    if(count($b) > 0){
                        $r = array_merge($r, $b);
                    }
                }
            }
        }
        return $r;
    }

    /**
     * Get elements by attribute and value combination recursively
     * @param string $name
     * @return RaiseCollection
     */
    public function getByAttrValue($name, $value){
        return new RaiseCollection($this->searchByAttrValue($this, $name, $value));
    }

    /**
     * Search the entire DOM tree by tag name
     * @param RaiseDomTreeNode $a
     * @param string $id
     * @return array
     */
    private function searchByAttrValue($a, $k, $v){
        $r = array();
        if($a->children() != null){
            foreach($a->children() as $item){
                if($item->tag() != null){
                    if($item->tag()->attributes()->get($k) == $v){
                        $r[] = $item;
                    }
                }
                if($item->children() != null){
                    $b = $this->searchByAttrValue($item, $k, $v);
                    if(count($b) > 0){
                        $r = array_merge($r, $b);
                    }
                }
            }
        }
        return $r;
    }

    /**
     * Get elements by tag name recursively
     * @param string $name
     * @return RaiseCollection
     */
    public function getByTagName($name){
        return new RaiseCollection($this->searchByTagName($this, $name));
    }

    /**
     * Search the entire DOM tree by tag name
     * @param RaiseDomTreeNode $a
     * @param string $id
     * @return array
     */
    private function searchByTagName($a, $n){
        $r = array();
        if($a->children() != null){
            foreach($a->children() as $item){
                if($item->tag() != null){
                    if($item->tag()->tagName() == $n){
                        $r[] = $item;
                    }
                }
                if($item->children() != null){
                    $b = $this->searchByTagName($item, $n);
                    if(count($b) > 0){
                        $r = array_merge($r, $b);
                    }
                }
            }
        }
        return $r;
    }

    /**
     * Get an element by its ID recursively
     * @param string $id
     * @return RaiseDomTreeNode
     */
    public function getByElementId($id){
        return $this->searchById($this, $id);
    }

    /**
     * Search the entire DOM Tree for an element using the element ID
     * @param RaiseDomTreeNode $a
     * @param string $id
     * @return RaiseDomTreeNode
     */
    private function searchById($a, $id){
        if($a->children()){
            foreach($a->children() as $item){
                if($item->tag() != null){
                    if($item->tag()->attributes()->get('id') == $id){
                        return $item;
                    }
                }
                if($item->children() != null){
                    $t = $this->searchById($item, $id);
                    if($t != null){
                        return $t;
                    }
                }
            }
        }
        return null;
    }

}